﻿using FCSChart.Axis;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace FCSChart.Series
{
    /// <summary>
    /// 密度图
    /// </summary>
    public class DensitySeries : ISeries
    {
        /// <summary>
        /// 散点的点的宽度
        /// </summary>
        public int PointLength
        {
            get { return (int)GetValue(PointLengthProperty); }
            set { SetValue(PointLengthProperty, value); }
        }
        public static readonly DependencyProperty PointLengthProperty = DependencyProperty.Register("PointLength", typeof(int), typeof(DensitySeries), new PropertyMetadata(2, NeedRedrawingProperty_Changed));
        /// <summary>
        /// 密度梯度
        /// </summary>
        public byte GradesBase
        {
            get { return (byte)GetValue(GradesBaseProperty); }
            set { SetValue(GradesBaseProperty, value); }
        }
        public static readonly DependencyProperty GradesBaseProperty = DependencyProperty.Register("GradesBase", typeof(byte), typeof(DensitySeries), new PropertyMetadata((byte)0x04, NeedRedrawingProperty_Changed));

        public DensitySeries()
        {
            this.SeriesName = "Density";
        }

        internal override async void Drawing()
        {
            if (OwnerChart == null || OwnerChart.XSource == null || OwnerChart.YSource == null || OwnerChart.XAxis == null || OwnerChart.YAxis == null) return;
            if (CancelTokenSource != null)
            {
                CancelTokenSource.Cancel();
                CancelTokenSource.Dispose();
            }
            CancelTokenSource = new CancellationTokenSource();
            try
            {
                var temps = await GetGeometries(CancelTokenSource.Token, OwnerChart.XSource, OwnerChart.YSource, OwnerChart.XAxis, OwnerChart.YAxis, OwnerChart.Indexs, OwnerChart.XValueConverter, OwnerChart.YValueConverter, this.Fill, this.Stroke, this.GetGradientColor);
                if (temps != null)
                {
                    Geometries = temps;
                    Drawing(Geometries);
                }
                if (CancelTokenSource != null)
                {
                    CancelTokenSource.Dispose();
                    CancelTokenSource = null;
                    System.Diagnostics.Debug.WriteLine("密度图结束生成矢量数据");
                }
            }
            catch (TaskCanceledException) { }
        }

        internal override Task<List<StreamColor>> GetGeometries(CancellationToken token, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter, Color fill, Color stroke, Func<long, long, Color> getGradientColor)
        {
            var pointcount = Math.Min(xSource.Count, ySource.Count);
            var pointLength = this.PointLength;
            var gradesBase = this.GradesBase;
            var width = xAxis.ActualWidth;
            var height = yAxis.ActualHeight;
            Func<double, ValueLocationConvertParam, double> xGetLocation = xAxis.GetValueLocation;
            Func<double, ValueLocationConvertParam, double> yGetLocation = yAxis.GetValueLocation;
            var xParam = xAxis.GetConvertParam();
            var yParam = yAxis.GetConvertParam();
            var maxX = xAxis.Max;
            var minX = xAxis.Min;
            var maxY = yAxis.Max;
            var minY = yAxis.Min;
            var maxDegreeOfParallelism = MaxDegreeOfParallelism;
            var response = Task.Factory.StartNew(() =>
            {
                ConcurrentDictionary<int, ConcurrentDictionary<int, long>> datas = new ConcurrentDictionary<int, ConcurrentDictionary<int, long>>();
                for (int i = 0; i < width; i += pointLength)
                {
                    if (token.IsCancellationRequested) return null;
                    datas[i] = new ConcurrentDictionary<int, long>();
                    for (int j = 0; j < height; j += pointLength)
                    {
                        if (token.IsCancellationRequested) return null;
                        datas[i][j] = 0L;
                    }
                }
                if (indexs == null)
                {
                    try
                    {
                        var result = Parallel.For(0, pointcount, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                        {
                            if (loop.IsStopped) return;
                            if (xSource == null || ySource == null || xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                            var valueX = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                            var valueY = yValueConverter == null ? Convert.ToDouble(ySource[i]) : yValueConverter(ySource[i]);
                            AddValue(datas, valueX, valueY, maxX, minX, maxY, minY, xParam, yParam, xGetLocation, yGetLocation, pointLength);
                        });
                        if (!result.IsCompleted) return null;
                    }
                    catch (OperationCanceledException) { return null; }
                }
                else
                {
                    try
                    {
                        var result = Parallel.ForEach(indexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                        {
                            if (loop.IsStopped) return;
                            if (xSource == null || ySource == null || xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                            var valueX = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                            var valueY = yValueConverter == null ? Convert.ToDouble(ySource[i]) : yValueConverter(ySource[i]);
                            AddValue(datas, valueX, valueY, maxX, minX, maxY, minY, xParam, yParam, xGetLocation, yGetLocation, pointLength);
                        });
                        if (!result.IsCompleted) return null;
                    }
                    catch (OperationCanceledException) { return null; }
                }
                if (datas.Count <= 0) return null;
                var temphasvalues = datas.Values.Where(p => p.Values != null && p.Values.Count > 0);
                if (temphasvalues.Count() <= 0) return null;
                var maxCount = temphasvalues.Max(p => p.Values.Max(k => k));
                List<StreamColor> streams = new List<StreamColor>();
                List<StreamGeometryContext> sgcs = new List<StreamGeometryContext>();
                var count = maxCount == 0 ? 0 : Convert.ToInt32(Math.Log(maxCount, gradesBase));
                for (int i = 0; i <= count; i++)
                {
                    if (token.IsCancellationRequested) return null;
                    var streamGeometry = new StreamGeometry() { FillRule = FillRule.Nonzero };
                    streams.Add(new StreamColor() { Stream = streamGeometry, Fill = getGradientColor(i, count), Stroke = getGradientColor(i, count) });
                    StreamGeometryContext sgc = streamGeometry.Open();
                    sgcs.Add(sgc);
                }
                foreach (var data in datas)
                {
                    if (token.IsCancellationRequested) return null;
                    foreach (var temp in data.Value)
                    {
                        if (token.IsCancellationRequested) return null;
                        if (temp.Value <= 0L) continue;
                        var index = Convert.ToInt32(Math.Log(temp.Value, gradesBase));
                        sgcs[index].BeginFigure(new Point(data.Key, temp.Key), true, true);
                        sgcs[index].PolyLineTo(new Point[] { new Point(data.Key + pointLength, temp.Key), new Point(data.Key + pointLength, temp.Key + pointLength), new Point(data.Key, temp.Key + pointLength) }, false, false);
                    }
                }
                foreach (var sgc in sgcs)
                {
                    if (token.IsCancellationRequested) return null;
                    sgc.Close();
                }
                for (int i = 0; i < streams.Count; i++)
                {
                    if (token.IsCancellationRequested) return null;
                    var stream = streams[i];
                    stream.Stream.Freeze();
                }
                return streams;
            }, token);
            return response;
        }

        private void AddValue(ConcurrentDictionary<int, ConcurrentDictionary<int, long>> datas, double valueX, double valueY, double maxX, double minX, double maxY, double minY, ValueLocationConvertParam xParam, ValueLocationConvertParam yParam, Func<double, ValueLocationConvertParam, double> xGetLocation, Func<double, ValueLocationConvertParam, double> yGetLocation, int pointLength)
        {
            //if (valueX > maxX || valueX < minX || valueY > maxY || valueY < minY) return;
            if (valueX > maxX) valueX = maxX; else if (valueX < minX) valueX = minX;
            if (valueY > maxY) valueY = maxY; else if (valueY < minY) valueY = minY;
            var xLocation = xGetLocation(valueX, xParam);
            var yLocation = yGetLocation(valueY, yParam);
            if (double.IsNaN(xLocation) || double.IsInfinity(xLocation) || double.IsNaN(yLocation) || double.IsInfinity(yLocation)) return;
            var x1 = Math.Abs(xLocation % pointLength) > 0 ? ((Convert.ToInt32(xLocation) / pointLength - 1) * pointLength) : Convert.ToInt32(xLocation);
            var y1 = Math.Abs(yLocation % pointLength) > 0 ? ((Convert.ToInt32(yLocation) / pointLength - 1) * pointLength) : Convert.ToInt32(yLocation);
            if (datas.ContainsKey(x1) && datas[x1].ContainsKey(y1))
            {
                datas[x1][y1] += 1;
            }
        }

        #region 区域显示
        /// <summary>
        /// 添加区域
        /// </summary>
        /// <param name="area"></param>
        internal override void AddArea(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {

        }
        /// <summary>
        /// 删除区域
        /// </summary>
        /// <param name="area"></param>
        internal override void RemoveArea(Graphical.GraphicalArea area)
        {

        }
        /// <summary>
        /// 更新区域显示颜色
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaDisplayColor(Graphical.GraphicalArea area)
        {

        }
        /// <summary>
        /// 更新区域索引
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaIndexs(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {

        }
        #endregion
    }
}
